﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices; // DllImport

namespace FileWalker
{
	/// <summary>
	/// File and directory enumerator.
	/// </summary>
	public sealed class FileWalkEnumerator
	{
		#region "DllImports, Constants & Structs"

		private const Int32 INVALID_HANDLE_VALUE = -1;
		private const Int32 OPEN_EXISTING = 3;
		private const Int32 FILE_FLAG_OPEN_REPARSE_POINT = 0x200000;
		private const Int32 FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
		private const Int32 FSCTL_GET_REPARSE_POINT = 0x900A8;

		/// <summary>

		/// If the path "REPARSE_GUID_DATA_BUFFER.SubstituteName" 

		/// begins with this prefix,

		/// it is not interpreted by the virtual file system.

		/// </summary>

		private const String NonInterpretedPathPrefix = "\\??\\";

		[StructLayout(LayoutKind.Sequential)]
		private struct REPARSE_GUID_DATA_BUFFER
		{
			public UInt32 ReparseTag;
			public UInt16 ReparseDataLength;
			public UInt16 Reserved;
			public UInt16 SubstituteNameOffset;
			public UInt16 SubstituteNameLength;
			public UInt16 PrintNameOffset;
			public UInt16 PrintNameLength;

			/// <summary>
			/// Contains the SubstituteName and the PrintName.
			/// The SubstituteName is the path of the target directory.
			/// </summary>
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)]
			public byte[] PathBuffer;
		}

		//[DllImport("kernel32.dll", SetLastError = true)]
		//private static extern IntPtr CreateFile(String lpFileName,
		//                                        Int32 dwDesiredAccess,
		//                                        Int32 dwShareMode,
		//                                        IntPtr lpSecurityAttributes,
		//                                        Int32 dwCreationDisposition,
		//                                        Int32 dwFlagsAndAttributes,
		//                                        IntPtr hTemplateFile);

		//[DllImport("kernel32.dll", SetLastError = true)]
		//private static extern Int32 CloseHandle(IntPtr hObject);

		//[DllImport("kernel32.dll", SetLastError = true)]
		//private static extern Int32 DeviceIoControl(IntPtr hDevice,
		//                                             Int32 dwIoControlCode,
		//                                             IntPtr lpInBuffer,
		//                                             Int32 nInBufferSize,
		//                                             IntPtr lpOutBuffer,
		//                                             Int32 nOutBufferSize,
		//                                             out Int32 lpBytesReturned,
		//                                             IntPtr lpOverlapped);
		#endregion

		#region Fields

		/// <summary>
		/// Holds start directory name.
		/// </summary>
		private readonly string startFolder_;

		/// <summary>
		/// Holds IFWHandler list.
		/// </summary>
		private List<IFWHandler> fwHandlers_;

		private bool execute_;

		#endregion Fields

		#region Properties
		#endregion Properties

		#region Methods

		/// <summary>
		/// Prevents a default instance of the FileWalkEnumerator class from being created.
		/// </summary>
		private FileWalkEnumerator()
		{
		}

		public void Stop()
		{
			execute_ = false;
		}

		/// <summary>
		/// Initializes a new instance of the Disk class.
		/// </summary>
		/// <param name="startDirectory">Start directory name.</param>
		public FileWalkEnumerator(string startDirectory)
		{
			startFolder_ = startDirectory;
			fwHandlers_ = new List<IFWHandler>();
		}

		/// <summary>
		/// Adds IFWHandler.
		/// </summary>
		/// <param name="handler">IFWHandler to add.</param>
		public void AddHandler(IFWHandler handler)
		{
			fwHandlers_.Add(handler);
		}

		/// <summary>
		/// Performs enumeration.
		/// </summary>
		/// <remarks>
		/// Starts enumeration from the start directory.
		/// </remarks>
		/// <returns>true, if all files and directories was enumerated; otherwise - false.</returns>
		public bool Enumerate()
		{
			bool rslt = false;

			execute_ = true;

			if (0 != fwHandlers_.Count)
			{
				foreach (IFWHandler fwHandler in fwHandlers_)
				{
					fwHandler.FolderChanged(startFolder_, startFolder_);

					if (!execute_)
					{
						return false;
					}
				}

				if (!execute_)
				{
					return false;
				}

				rslt = Enumerate(startFolder_);
			}

			return rslt;
		}

		/// <summary>
		/// Enumerates directory.
		/// </summary>
		/// <remarks>
		/// Enumerates all files in the specified directory.
		/// <para/>The method calls all FileChanged in the handler list for each file in directory.
		/// <para/>Stops enumeration, if at least one handler returns false.
		/// <para/>Enumerates all subdirectories in the specfied directory.
		/// <para/>The method calls all FolderChanged in the handler list for each subdirectory in directory.
		/// <para/>Stops enumeration, if at least one handler returns false.
		/// <para/>The method calls recursively itself for each subdirectory in directory.
		/// </remarks>
		/// <param name="directoryName">Directory to enumearte.</param>
		/// <returns>true, if all files and subdirectories was enumerated; otherwise - false.</returns>
		private bool Enumerate(string directoryName)
		{
			bool is2Continue = execute_;

			if (is2Continue)
			{
				// Check, if it is a link.
				try
				{
					FileAttributes attrs = File.GetAttributes(directoryName);

					if (!((attrs & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
					{
						try
						{

							// Get file list.
							foreach (string fileName in Directory.GetFiles(directoryName))
							{
								if (!execute_)
								{
									return false;
								}

								foreach (IFWHandler fwHandler in fwHandlers_)
								{
									if (!execute_)
									{
										return false;
									}

									if (!(is2Continue = fwHandler.FileChanged(startFolder_, fileName)))
									{
										break;
									}
								}

								if (!is2Continue)
								{
									break;
								}
							}
						}

						catch (UnauthorizedAccessException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The caller does not have the required permission.{0}Error: {1}",
											System.Environment.NewLine, excp.Message),
											"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
							is2Continue = false;
						}

						catch (ArgumentException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is not valid.{1}Error: {2}",
											directoryName, System.Environment.NewLine, excp.Message),
											"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
							is2Continue = false;
						}

						catch (PathTooLongException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is too long.{1}Error: {2}",
											directoryName, System.Environment.NewLine, excp.Message),
											"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
							is2Continue = false;
						}

						catch (DirectoryNotFoundException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is not found.{1}Error: {2}",
											directoryName, System.Environment.NewLine, excp.Message),
											"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
							is2Continue = false;
						}

						catch (IOException excp)
						{
							System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is file name.{1}Error: {2}",
											directoryName, System.Environment.NewLine, excp.Message),
											"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
							is2Continue = false;
						}

						if (is2Continue && execute_)
						{
							try
							{
								foreach (string subDirectoryName in Directory.GetDirectories(directoryName))
								{
									if (!execute_)
									{
										return false;
									}

									foreach (IFWHandler fwHandler in fwHandlers_)
									{
										if (!execute_)
										{
											return false;
										}

										if (!(is2Continue = fwHandler.FolderChanged(startFolder_, subDirectoryName)))
										{
											break;
										}
									}

									if (execute_)
									{
										if (is2Continue)
										{
											Enumerate(subDirectoryName);
										}
										else
										{
											is2Continue = true;
										}
									}
									else
									{
										return false;
									}
								}
							}
							catch (UnauthorizedAccessException excp)
							{
								System.Windows.MessageBox.Show(String.Format("The caller does not have the required permission.{0}Error: {1}",
												System.Environment.NewLine, excp.Message),
												"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
								is2Continue = false;
							}

							catch (ArgumentException excp)
							{
								System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is not valid.{1}Error: {2}",
												directoryName, System.Environment.NewLine, excp.Message),
												"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
								is2Continue = false;
							}

							catch (PathTooLongException excp)
							{
								System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is too long.{1}Error: {2}",
												directoryName, System.Environment.NewLine, excp.Message),
												"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
								is2Continue = false;
							}

							catch (DirectoryNotFoundException excp)
							{
								System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is not found.{1}Error: {2}",
												directoryName, System.Environment.NewLine, excp.Message),
												"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
								is2Continue = false;
							}

							catch (IOException excp)
							{
								System.Windows.MessageBox.Show(String.Format("The path \"{0}\" is file name.{1}Error: {2}",
												directoryName, System.Environment.NewLine, excp.Message),
												"Folder creation", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
								is2Continue = false;
							}
						}
					}
				}
				catch
				{
				}
			}

			if (!execute_)
			{
				return false;
			}

			return is2Continue;
		}

		/// <summary>
		/// Gets the target directory from a directory link in Windows Vista.
		/// 
		/// Author:  Manfred Bittersam
		/// http://www.codeproject.com/KB/vista/Windows_Vista.aspx
		/// 
		/// </summary>
		/// <param name="directoryInfo">The directory info of this directory 
		/// link</param>
		/// <returns>the target directory, if it was read, 
		/// otherwise an empty string.</returns>
		public static String GetTargetDir(DirectoryInfo directoryInfo)
		{
			String targetDir = "";

			try
			{
				// Is it a directory link?
				if ((directoryInfo.Attributes
					& FileAttributes.ReparsePoint) != 0)
				{
					// Open the directory link:
					IntPtr hFile = Copy4Backup.NativeMethods.CreateFile(directoryInfo.FullName,
												0,
												0,
												IntPtr.Zero,
												OPEN_EXISTING,
												FILE_FLAG_BACKUP_SEMANTICS |
												FILE_FLAG_OPEN_REPARSE_POINT,
												IntPtr.Zero);
					if (hFile.ToInt32() != INVALID_HANDLE_VALUE)
					{
						// Allocate a buffer for the reparse point data:
						Int32 outBufferSize = Marshal.SizeOf
							(typeof(REPARSE_GUID_DATA_BUFFER));
						IntPtr outBuffer = Marshal.AllocHGlobal(outBufferSize);

						try
						{
							// Read the reparse point data:
							Int32 bytesReturned;
							Int32 readOK = Copy4Backup.NativeMethods.DeviceIoControl(hFile,
													   FSCTL_GET_REPARSE_POINT,
															IntPtr.Zero,
															0,
															outBuffer,
															outBufferSize,
															out bytesReturned,
															IntPtr.Zero);
							if (readOK != 0)
							{
								// Get the target directory from the reparse 
								// point data:
								REPARSE_GUID_DATA_BUFFER rgdBuffer =
									(REPARSE_GUID_DATA_BUFFER)
									Marshal.PtrToStructure
								 (outBuffer, typeof(REPARSE_GUID_DATA_BUFFER));
								targetDir = Encoding.Unicode.GetString
										  (rgdBuffer.PathBuffer,
										  rgdBuffer.SubstituteNameOffset,
										  rgdBuffer.SubstituteNameLength);
								if (targetDir.StartsWith
									(NonInterpretedPathPrefix, StringComparison.CurrentCultureIgnoreCase))
								{
									targetDir = targetDir.Substring
						(NonInterpretedPathPrefix.Length);
								}
							}
						}
						catch (Exception)
						{
						}

						// Free the buffer for the reparse point data:
						Marshal.FreeHGlobal(outBuffer);

						// Close the directory link:
						Copy4Backup.NativeMethods.CloseHandle(hFile);
					}
				}
			}
			catch (Exception)
			{
			}

			return targetDir;
		}
		#endregion Methods
	}
}
